// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Jeff Stewart
//
// This is the second screen in the wizard.  It asks the user to identify
// their mail client and configuration, and it collects user credentials.
//
// This is a complicated screen which merits some explanation.
// This page accomplishes two tasks:
//    Collect the email program and profile information
//    Collect username and password
//
// The display rules are as follows:
//   Dialog starts out with default mail program selected and first
//   profile selected.
//   Focus SHOULD be in the password box, but I haven't made that work
//   yet and it looks like that may not be possible.
//   If user chooses "Other" from the program list, the profile list
//   disappears and is replaced with an email address text line edit
//   If the user chooses "My account is not here" from the profile list,
//   then we keep the profile box but add the email address text line
//   edit.
//
//   The text line edit is populated from the profile popdown with the
//   last selected value.
//   "Next" will be enabled when a username/profile and password are
//   filled in.  For an explicitly set username, it must match our
//   regexp.  If populated from a profile, we'll test whatever they
//   configure.


#include <QtCore/QSet>
#include <QtCore/QTextStream>
#include <QtGui/QComboBox>
#include <QtGui/QPushButton>
#include <QtGui/QTextBrowser>

#include "ui/clientconfigscreen.h"
#include "ui/introscreen.h"
#include "ui/wizard.h"
#include "util/protocol.h"
#include "util/provider.h"

namespace popdiag {

ClientConfigScreen::ClientConfigScreen(QWidget *parent)
    : QWizardPage(parent), current_result_(NULL),
      program_combo_(new QComboBox(this)),
      profile_label_(new QLabel(this)),
      profile_combo_(new QComboBox(this)),
      email_address_label_(new QLabel(this)),
      email_example_label_(new QLabel(this)),
      email_address_field_(new QLineEdit(this)),
      password_field_(new QLineEdit(this)),
      password_label_(new QLabel(this)),
      imap_server_label_(new QLabel(this)),
      pop_server_label_(new QLabel(this)),
      smtp_server_label_(new QLabel(this)),
      smtp_port_example_label_(new QLabel(this)),
      imap_server_field_(new QLineEdit(this)),
      pop_server_field_(new QLineEdit(this)),
      smtp_server_field_(new QLineEdit(this)),
      smtp_port_field_(new QLineEdit(this)) {
}

ClientConfigScreen::~ClientConfigScreen() {
}

void ClientConfigScreen::SetupUI() {
  QGridLayout *layout = new QGridLayout(this);
  QLabel *title = new QLabel(this);
  QString title_text = "<h1>";
  title_text += tr("Step 1/2: Checking your configuration",
                   "Title on the first user-interactive wizard page");
  title_text += "</h1><p></p>";
  title->setText(title_text);
  title->setWordWrap(true);
  layout->addWidget(title, 0, 0, 1, 2, Qt::AlignLeading);

  QLabel *explanation = new QLabel(this);
  QString explanation_text;
  explanation_text += tr("We are going to check your configuration. "
                         "Please enter your account information.",
                         "Asks the user to enter account information");
  explanation->setText(explanation_text);
  explanation->setWordWrap(true);
  layout->addWidget(explanation, 1, 0, 2, 2, Qt::AlignLeading);

  QLabel *program_label = new QLabel(this);
  program_label->setText(tr("Email Software",
                            "Label for a pulldown list of "
                            "email reading programs"));
  layout->addWidget(program_label, 3, 0, Qt::AlignTrailing);
  layout->addWidget(program_combo_, 3, 1, Qt::AlignLeading);
  CHECK(connect(program_combo_, SIGNAL(currentIndexChanged(int)),
                this, SLOT(OnProgramSelected(int))));
  CHECK(connect(profile_combo_, SIGNAL(currentIndexChanged(int)),
                this, SLOT(OnProfileSelected(int))));

  profile_label_->setText(
      tr("Profile", "A label for a pulldown combo box showing email profiles"));
  profile_combo_->clear();
  profile_combo_->setToolTip(tr("Choose the settings within your "
                                "email program to use.",
                                "A help label for the 'Email Program' label"));

  program_combo_->setMinimumContentsLength(25);
  program_combo_->setSizeAdjustPolicy(
      QComboBox::AdjustToMinimumContentsLength);
  profile_combo_->setMinimumContentsLength(35);
  profile_combo_->setSizeAdjustPolicy(
      QComboBox::AdjustToMinimumContentsLength);

  layout->addWidget(profile_label_, 4, 0, Qt::AlignTrailing);
  layout->addWidget(profile_combo_, 4, 1, Qt::AlignLeading);

  if (provider->NeedEmailAddress()) {
    email_address_label_->setText(tr("&Email Address",
                                    "A label for an input field.  "
                                    "The ampersand "
                                    "marks the keyboard accelerator "
                                    "for this field."));
  } else {
    email_address_label_->setText(tr("&Account Name",
                                    "A label for an input field.  "
                                    "The ampersand "
                                    "marks the keyboard accelerator "
                                    "for this field."));
  }
  // UI "hints" are in grey.
  email_example_label_->setText("<font color='grey'>" +
                                tr("e.g. %1",
                                   "Sample email address")
                                  .arg(provider->SampleEmailAddress())
                                + "</font>");

  email_address_label_->setBuddy(email_address_field_);
  registerField("email", email_address_field_);
  email_address_field_->setMaxLength(256);

  layout->addWidget(email_address_label_, 5, 0, Qt::AlignTrailing);
  layout->addWidget(email_address_field_, 5, 1, Qt::AlignLeading);
  layout->addWidget(email_example_label_, 6, 1, Qt::AlignLeading);

  CHECK(connect(email_address_field_,
                SIGNAL(textChanged(const QString &)),
        this, SIGNAL(completeChanged())));

  // makes "P" a keyboard shortcut for this field
  password_label_->setText(tr("&Password", "A label for an input field.  "
                              "The ampersand marks the keyboard "
                              "accelerator for this field."));

  password_label_->setBuddy(password_field_);
  password_field_->setEchoMode(QLineEdit::Password);
  password_field_->setMaxLength(256);

  password_field_->setFocus(Qt::OtherFocusReason);
  QLabel *password_storage_label = new QLabel(this);
  password_storage_label->setText("<font color='grey'>" +
                                tr("Your password will be stored securely "
                                   "and deleted upon completion of "
                                   "the process.",
                                   "Explains that the password is not stored "
                                   "permanently")
                                + "</font>");
  password_storage_label->setWordWrap(true);

  registerField("password", password_field_);
  layout->addWidget(password_label_, 7, 0, Qt::AlignTrailing);
  layout->addWidget(password_field_, 7, 1, Qt::AlignLeading);
  layout->addWidget(password_storage_label, 8, 1, 2, 1, Qt::AlignLeading);

  CHECK(connect(password_field_, SIGNAL(textChanged(const QString &)),
        this, SIGNAL(completeChanged())));

  QCheckBox *fetch_all_mail = new QCheckBox(tr("Retrieve all mail",
                                            "A checkbox label asking whether "
                                            "to retrieve all messages"));
  fetch_all_mail->setToolTip(tr("Normally, only the first message in your "
                                "inbox will be retrieved for the test. "
                                "With this option, all messages will "
                                " be retrieved instead.",
                                "Explains the exact effect of the checkbox"));
  fetch_all_mail->setCheckState(Qt::Unchecked);
  registerField("fetchAllMail", fetch_all_mail);
  layout->addWidget(fetch_all_mail, 10, 1, Qt::AlignLeading);

  if (provider->PredefinedServers()) {
    imap_server_field_->hide();
    imap_server_label_->hide();
  } else {
    imap_server_label_->setText(tr("IMAP Server",
                                   "A label for an input field."));
    imap_server_label_->setBuddy(imap_server_field_);
    imap_server_field_->setMaxLength(256);
    layout->addWidget(imap_server_label_, 11, 0, Qt::AlignTrailing);
    layout->addWidget(imap_server_field_, 11, 1, Qt::AlignLeading);

    CHECK(connect(imap_server_field_, SIGNAL(textChanged(const QString &)),
          this, SIGNAL(completeChanged())));
  }
  registerField("imapServer", imap_server_field_);

  if (provider->PredefinedServers()) {
    pop_server_field_->hide();
    pop_server_label_->hide();
  } else {
    pop_server_label_->setText(tr("POP Server",
                                   "A label for an input field."));
    pop_server_label_->setBuddy(pop_server_field_);
    pop_server_field_->setMaxLength(256);
    layout->addWidget(pop_server_label_, 12, 0, Qt::AlignTrailing);
    layout->addWidget(pop_server_field_, 12, 1, Qt::AlignLeading);

    CHECK(connect(pop_server_field_, SIGNAL(textChanged(const QString &)),
          this, SIGNAL(completeChanged())));
  }
  registerField("popServer", pop_server_field_);

  if (provider->PredefinedServers()) {
    smtp_server_field_->hide();
    smtp_server_label_->hide();
    smtp_port_field_->hide();
    smtp_port_example_label_->hide();
  } else {
    QGridLayout *child_layout = new QGridLayout();
    smtp_server_label_->setText(tr("SMTP Server/Port",
                                   "A label for an input field."));
    smtp_server_label_->setBuddy(smtp_server_field_);
    smtp_server_field_->setMaxLength(256);
    layout->addWidget(smtp_server_label_, 13, 0, Qt::AlignTrailing);
    child_layout->addWidget(smtp_server_field_, 0, 1, Qt::AlignLeading);
    smtp_port_field_->setValidator(new QIntValidator(1, 65535, this));
    child_layout->addWidget(smtp_port_field_, 0, 2, Qt::AlignLeading);
    smtp_port_example_label_->setText("<font color='grey'>" +
                                      tr("e.g. 25, 465, or 587",
                                         "An example for the SMTP port "
                                         "input field.")
                                      + "</font>");
    child_layout->addWidget(smtp_port_example_label_, 1, 2, Qt::AlignLeading);
    layout->addLayout(child_layout, 13, 1, 2, 1, Qt::AlignLeading);

    CHECK(connect(smtp_server_field_, SIGNAL(textChanged(const QString &)),
          this, SIGNAL(completeChanged())));
    CHECK(connect(smtp_port_field_, SIGNAL(textChanged(const QString &)),
          this, SIGNAL(completeChanged())));
  }
  registerField("smtpServer", smtp_server_field_);
  registerField("smtpPort", smtp_port_field_);
}

// user picked a program from the pulldown.
// also called at startup with value 0.
void ClientConfigScreen::OnProgramSelected(int index) {
  current_result_ = NULL;
  int max_item_index = program_combo_->count() - 1;
  bool is_none = (index == max_item_index);
  profile_combo_->clear();
  if (!is_none) {
    ResultMap::iterator i = results_.find(program_combo_->currentText());
    CHECK(i != results_.end());
    // TODO: Pick the default profile once I update the config
    //       objects to expose that data
    for (ResultList::iterator j = i.value().begin();
         j != i.value().end(); j++) {
      QString profile_entry = tr("%3: %1 (%2)",
                                 "Rendering of an email address, name, and "
                                 "protocol. Protocol will be POP or IMAP. "
                                 "%1 is the email address, %2 is the name, "
                                 "%3 is the protocol.")
                              .arg((*j)->display_email())
                              .arg((*j)->display_name())
                              .arg((*j)->display_protocol());
      profile_combo_->addItem(profile_entry);
    }
    profile_combo_->addItem(tr("My account is not here",
                               "A 'none of the above' option for picking a "
                               "configuration set in an email program"));
    OnProfileSelected(0);
  }
  ShowEmailAndServerFields(is_none);
  ShowProfile(!is_none);
}

// user picked a profile from the pulldown.
// also called at startup with value 0.
void ClientConfigScreen::OnProfileSelected(int index) {
  current_result_ = NULL;
  if (index == -1) {
    ShowEmailAndServerFields(false);
    emit ConfigChosen(NULL);
    // profile de-selected;
    return;
  }
  bool show_email = index == profile_combo_->count() - 1;
  ShowEmailAndServerFields(show_email);
  if (!show_email) {
    ResultMap::iterator program = results_.find(program_combo_->currentText());
    CHECK(program != results_.end());
    current_result_ = program.value().at(index);
    // clone the found email address to the field for use by other pages:
    wizard()->setField("email", current_result_->display_email());
    emit ConfigChosen(current_result_);
  }
}

void ClientConfigScreen::ShowProfile(bool show) {
  if (show) {
    profile_label_->show();
    profile_combo_->show();
  } else {
    profile_label_->hide();
    profile_combo_->hide();
  }
}

void ClientConfigScreen::ShowEmailAndServerFields(bool show) {
  email_address_field_->setVisible(show);
  email_address_label_->setVisible(show);
  email_example_label_->setVisible(show);
  if (!provider->PredefinedServers()) {
    imap_server_field_->setVisible(show);
    imap_server_label_->setVisible(show);
    pop_server_field_->setVisible(show);
    pop_server_label_->setVisible(show);
    smtp_server_field_->setVisible(show);
    smtp_server_label_->setVisible(show);
    smtp_port_field_->setVisible(show);
    smtp_port_example_label_->setVisible(show);
  }
  if (show) {
    QRegExp email_regexp(provider->AccountValidationExpression());
    email_address_field_->setValidator(
        new QRegExpValidator(email_regexp, this));
    email_address_field_->setFocus(Qt::OtherFocusReason);
  } else {
    // accept non-email addresses from configured profiles:
    email_address_field_->setValidator(NULL);
    password_field_->setFocus(Qt::OtherFocusReason);
  }
}

bool ClientConfigScreen::validatePage() {
  // can be used to suppress "next" from working.
  // We use it to derive pop and smtp server
  // hosts to connect to by default.
  QString email = field("email").toString();
  if (provider->PredefinedServers()) {
    setField("imapServer", provider->ImapServer());
    setField("popServer", provider->PopServer());
    setField("smtpServer", provider->SmtpServer());
    setField("smtpPort", QString::number(provider->SmtpPort()));
  }
  emit ConfigChosen(current_result_);
  return true;
}

void ClientConfigScreen::cleanupPage() {
  QWizardPage::cleanupPage();
  config_results_.clear();
  results_.clear();
  program_combo_->clear();
  profile_combo_->clear();
}

void ClientConfigScreen::initializePage() {
  QWizardPage::initializePage();
  ConfigChecker checker(NULL);
  VerificationSet faults_found;
  checker.GatherConfigDetails(&faults_found);
  OnConfigResultsCollected(faults_found);

  // This is mainly for debugging so that one doesn't have to enter
  // this information each time again to proceed to the following
  // wizard pages.
  char *email_from_env = getenv("POP_IMAP_TROUBLESHOOTER_EMAIL_ADDRESS");
  if (email_from_env != NULL)
    setField("email", email_from_env);
  char *password_from_env = getenv("POP_IMAP_TROUBLESHOOTER_PASSWORD");
  if (password_from_env != NULL)
    setField("password", password_from_env);
}


void ClientConfigScreen::OnConfigResultsCollected(const VerificationSet &vs) {
  // construct a two level mapping
  config_results_ = vs;
  results_.clear();
  QSet<QString> programs;
  QString default_program;
  for (VerificationSet::iterator i = config_results_.begin();
       i != config_results_.end(); i++) {
    VerificationResult *vr = *i;
    if (vr->is_default_program()) {
      default_program = vr->program_name();
    }
    programs.insert(vr->program_name());
    results_[vr->program_name()].append(vr);
  }

  program_combo_->clear();

  int default_index = -1;
  for (QSet<QString>::const_iterator i = programs.begin();
       i != programs.end(); i++) {
    program_combo_->addItem(*i);
    if (*i == default_program) {
      default_index = program_combo_->count() - 1;
    }
  }
  QString other_label = tr("Other",
                           "Displayed in a pulldown list to indicate that "
                           "none of the offered items are correct.");
  program_combo_->addItem(other_label);
  if (default_index == -1) {
    // select "other"
    default_index = program_combo_->count() - 1;
  }
  program_combo_->setCurrentIndex(default_index);
  // when default_index is 0, the selection doesn't
  // actually change, so our selection code doesn't fire.
  // fire it by hand:
  OnProgramSelected(default_index);
}

bool ClientConfigScreen::isComplete() const {
  if (field("email").toString() == "" || field("password").toString() == "")
    return false;
  // The order in which this function and OnProfileSelected() are
  // called is not fixed. Thus, access the combo directly here.
  // "Other" is always listed first, so if it is selected and there
  // are no predefined servers, check if there are specified ones.
  if (!provider->PredefinedServers() &&
      program_combo_->count() - 1 == program_combo_->currentIndex()) {
    if (field("imapServer").toString() == "" &&
        field("popServer").toString() == "")
      return false;
    if (field("smtpServer").toString() == "")
      return false;
    int port = field("smtpPort").toString().toInt();
    if (port <= 0 || port > 65535)
      return false;
  }
  return true;
}

}  // namespace popdiag
